{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## <center> Открытый курс по машинному обучению. Сессия № 3\n",
    "\n",
    "### <center> Автор материала: Александр Ничипоренко"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## <center> Индивидуальный проект по анализу данных </center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Данные лежат здесь: https://yadi.sk/d/mJbzt5pV3Uf5Zt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from matplotlib import pyplot as plt\n",
    "from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
    "from sklearn.pipeline import make_pipeline\n",
    "from sklearn.model_selection import cross_val_score, TimeSeriesSplit, GridSearchCV\n",
    "from sklearn.metrics import accuracy_score,classification_report,f1_score,roc_auc_score,roc_curve,precision_recall_curve\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from lightgbm import LGBMClassifier as lgbmc\n",
    "from catboost import CatBoostClassifier as catc\n",
    "from xgboost import XGBClassifier as xgbc\n",
    "plt.rcParams['figure.figsize'] = (20,20)\n",
    "sns.set(style=\"darkgrid\");\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 1. Описание набора данных и признаков."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Одной из главных целей для любой компании является удержание своих клиентов. В торговле успехом данного процесса является совершение повторных покупок клиентами в интервал времени, который характеризует потребление различных видов товаров:\n",
    "\n",
    "- Продукты: каждый день - еженедельно;\n",
    "- Хозтовары: каждые 2 недели - месяц;\n",
    "- Одежда: раз в три месяца - раз в полгода;\n",
    "- Крупная и дорогая электроника: раз в 1-2 года;\n",
    "- Автомобиль: раз в 3-5 лет.\n",
    "\n",
    "В данном проекте будут исследованы данные одного заказчика (менеджеров крупного интернет-гипермаркета, основным ассортиментом которого являются товары повседневного спроса) и построена модель, предсказывающая вероятность оттока клиента.\n",
    "\n",
    "Заказчик определил отток таким образом: клиент не сделает повторный заказ в течение трёх месяцев.\n",
    "Такая постановка обусловлена тем, что почти 80% клиентов делают свой повторный заказ в течение 3-х месяцев. Таким образом поставлена цель научиться определять 20% клиентов, которые этого не сделают. \n",
    "\n",
    "После этого, уже можно разрабатывать различные подходы к стимулированию данного пула клиентов к повторной сделке с помощью различных маркетинговых методов.\n",
    "\n",
    "Данные были получены от заказчика и сведены в один DataFrame. Посмотрим на них.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('data.csv',index_col='Client')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for index,value in enumerate(df.columns):\n",
    "    print (index,\":\",value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно у нас **273** столбца, целевая переменная - **target**, 271 - количественный и 1 категориальный признак(\"Y M\" = \"Год Месяц\").\n",
    "Каждая строка - описание клиента (история его покупок за текущий и предыдущие 6 месяцев) в месяц последней покупки.\n",
    "\n",
    "#### Теперь о количественных признаках.\n",
    "\n",
    "###### Сокращения:\n",
    "- **R** - Revenue - Выручка от продажи;\n",
    "- **S** - Strings - Кол-во строк - разных позиций (артикулов);\n",
    "- **O** - Orders - Кол-во заказов;\n",
    "- **Q** - Qnt - Кол-во штук;\n",
    "\n",
    "- **R_1 ... R_6, R_NOW**- Выручка по месяцам. NOW - месяц, соответствующий Y_M, _6 - предыдущий, _1 - 6 месяцев назад.\n",
    "- **Month**: от 1 до 12 (январь-декабрь).\n",
    "- **Y** или **N** в **R_Y_NOW, O_Y_NOW, R_N_NOW, R_N_NOW** - выручка/заказы в зависимости от способа оформления заказа. **Y** - через сайт, **N** - по телефону.\n",
    "\n",
    "- **Orders-1003 ... Orders-Other** - заказы за 7 месяцев (от _1 до _NOW) по выделенной группе товаров (70 \"топовых\" групп: 1003, ..., 931) или по остальным (Other). Аналогично и с выручкой и кол-ву штук.\n",
    "\n",
    "- **Other** - Кол-во групп товаров, купленных за 7 месяцев, входящих в группу \"Other\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 2. Первичный анализ признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим количество пропусков в данных. Как видно их нет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sum(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим среднее количество \"отточных клиентов\" в наборе данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print ('% клиентов, склонных к оттоку:', round(df['target'].mean()*100,2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получается даже меньше 20%. Выборка не сбалансирована."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "##### Посмотрим сколько у нас \"отточных\" клиентов ежемесячно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "churn=pd.crosstab(index=df['Y_M'],columns=df['target'])\n",
    "churn['%']=round(churn[1]/churn[0]*100,2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "churn.T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "churn_m=pd.crosstab(index=df['Month'],columns=df['target'])\n",
    "churn_m['%']=round(churn_m[1]/churn_m[0]*100,2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "churn_m.T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видна некоторая сезонность, в конце года \"отточных\" клиентов больше, летом - меньше. Осенью клиенты делают закупки активнее."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Числовых показателей у нас много. Будем рассматривать их небольшими группами.\n",
    "Для начала посмотрим на статистичекское описание ежемесячных показателей."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.drop(columns=['target','Month']).iloc[:,:29].describe().T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Можно заметить, что:\n",
    "\n",
    "- Масштабы признаков сильно различаются (наиболее сильно: выручка и заказы);\n",
    "- Есть отрицательные значения - это возвраты;\n",
    "- Статистики по предыдущим периодам сильно скошены (среднее больше медианы) из-за того, что много нулей и величины распределены не нормально;\n",
    "- Максимальные значения - очень сильно отличаются от средних, кто-то покупает покупает много товаров или дорогие товары;\n",
    "- Обычно клиенты делают один заказ в течение месяца;\n",
    "- В предыдущий месяц (_6) клиенты заказывают меньше, чем в другие предыдущие."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Посмотрим на значения показателей (выручка, заказы) по способу оформления заказа."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.drop(columns=['target','Month']).iloc[:,29:29+14*2].describe().T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заметно, что преобладают заказы, оформленные через Интернет."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Посмотрим разницу по показателям в зависимости от целевого признака."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ch_1=df[df['target']==1].drop(columns=['target','Month']).iloc[:,:29].describe().T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ch_0=df[df['target']==0].drop(columns=['target','Month']).iloc[:,:29].describe().T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ch_0-ch_1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, клиенты, которые нас интересуют - покупают меньше. В особенности, в предыдущие периоды."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Посмотрим, какие товары заказывают и на какие товары тратят деньги наши клиенты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "goods=pd.pivot_table(data=df,values=df.iloc[:,202:273],columns=df['target'],aggfunc=np.sum)\n",
    "goods['%_churn']=goods[1]/goods[0]*100\n",
    "goods.sort_values(by='%_churn',ascending=False).head(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, по разным категориям товаров доля затраченных денег отличается. Таким образом, если клиент потратил сумму на какую-то группу товаров, то вероятность его ухода как понижается, так и повышается в зависимости от этой группы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "goods_ord=pd.pivot_table(data=df,values=df.iloc[:,60:131],columns=df['target'],aggfunc=np.sum)\n",
    "goods_ord['%_churn']=goods_ord[1]/goods_ord[0]*100\n",
    "goods_ord.sort_values(by='%_churn',ascending=False).head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.heatmap(np.corrcoef(goods_ord['%_churn'],goods['%_churn']));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, заказы и деньги по товарным категориям коррелируют."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Посмотрим, сколько товарных групп из \"Other\" покупают разные клиенты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Other_0=df[df['target']==0]['Other'].describe()\n",
    "Other_1=df[df['target']==1]['Other'].describe()\n",
    "Other=pd.concat([Other_0,Other_1],axis=1,names=['Total','1'])\n",
    "Other.columns=['0','1']\n",
    "Other"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, уходящие клиенты покупают обычно в два раза меньше товаров из категории \"Другое\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 3. Первичный визуальный анализ признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Визуализируем распределение целевого класса."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[8, 5])\n",
    "sns.countplot(df['target']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее будем исследовать распределения признаков в зависимости от значения **\"target\"**. Для скошенных влево распределений будем применять **log(1+x)** преобразование и отсекать экстремально большие значения (>95%-99% квантили)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1,8):\n",
    "    plt.subplot(3, 3, i)\n",
    "    sns.distplot(np.log1p(df[df['target']==1].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(np.log1p(df[df['target']==0].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('log1x')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1,8):\n",
    "    plt.subplot(3, 3, i)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<95% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8,8+8):\n",
    "    plt.subplot(3, 3, i-8)\n",
    "    sns.distplot(np.log1p(df[df['target']==1].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(np.log1p(df[df['target']==0].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('log1x')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8,8+8):\n",
    "    plt.subplot(3, 3, i-8)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i],kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i],kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<95% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7,8+8+7):\n",
    "    plt.subplot(3, 3, i-8-7)\n",
    "    sns.distplot(np.log1p(df[df['target']==1].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(np.log1p(df[df['target']==0].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('log1x')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7,8+8+7):\n",
    "    plt.subplot(3, 3, i-8-7)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i],kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i],kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<99% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7,8+8+7+7):\n",
    "    plt.subplot(3, 3, i-8-7-7)\n",
    "    sns.distplot(np.log1p(df[df['target']==1].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(np.log1p(df[df['target']==0].iloc[:,i].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('log1x')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7,8+8+7+7):\n",
    "    plt.subplot(3, 3, i-8-7-7)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<95% quantile')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**По распределениям показателей за месяца можно увидеть, что:**\n",
    "- В месяц \"NOW\" у \"отточных\" клиентов сумма отгрузки, кол-во артикулов, штук и заказов меньше;\n",
    "- В месяц \"NOW\" у \"отточных\" клиентов пик на уровне 4000;\n",
    "- В предыдущие месяца у \"отточных клиентов\" меньше заказов и они реже;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7+8,8+8+7+7+8):\n",
    "    plt.subplot(3, 3, i-8-7-7-8)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<95% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7+8+7,8+8+7+7+8+7):\n",
    "    plt.subplot(3, 3, i-8-7-7-8-7)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.95))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<95% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7+8+7+7,8+8+7+7+8+7+7):\n",
    "    plt.subplot(3, 3, i-8-7-7-8-7-7)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<99% quantile')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[20, 15])\n",
    "for i in range(1+8+7+7+8+7+7+7,8+8+7+7+8+7+7+7):\n",
    "    plt.subplot(3, 3, i-8-7-7-8-7-7-7)\n",
    "    sns.distplot(df[(df['target']==1) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='r',label='target: 1')\n",
    "    sns.distplot(df[(df['target']==0) & (df.iloc[:,i]<df.iloc[:,i].quantile(0.99))].iloc[:,i].apply(lambda x: 0 if x<0 else x),kde=False,norm_hist=True,color='g',label='target: 0')\n",
    "    plt.legend()\n",
    "    plt.title('<99% quantile')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По способу оформления заказа клиенты мало отличаются, все предпочитают - Интернет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.factorplot(x='Other',y='target',data=df,kind='bar',size=5,aspect=2.8);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По распределению доли отточных клиентов, видна обратная зависимость от количества товарных групп (ТГ) из \"Другое\", чем меньше ТГ - тем больше доля оттока."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.factorplot(x='Month',y='target',data=df,kind='bar',size=4,aspect=2.2);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Доля оттока зависит от рассматриваемого месяца текущей закупки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=[35, 35])\n",
    "sns.heatmap(df.drop(columns=['target','Month']).corr(),cmap=\"RdBu_r\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из корреляционной матрицы видно, что есть коррелирующие признаки:\n",
    "- Заказы, выручка и штуки по товарным группам и по времени;\n",
    "- Одинаковые признаки по времени;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 4. Закономерности, \"инсайты\", особенности данных."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Найдены и выдвинуты предположения о природе различных корреляций/пропусков/закономерностей и выбросов, найденных в предыдущих пунктах. Есть пояснение, почему они важны для решаемой задачи;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**По проведённому анализу можно сделать выводы:**\n",
    "- Клиенты, которые не закупятся в ближайшие 3 месяца, реже закупались в предыдущие полгода;\n",
    "- Данные клиенты покупали меньше товаров из категории \"Other\";\n",
    "- Пик распределения суммы отгрузки в момент \"NOW\" у данных клиентов находится в районе 4000;\n",
    "- Есть сезонная (по месяцам) зависимость;\n",
    "- Признаки, связанные со способом оформления заказа малоинформативны;\n",
    "- Есть корреляции заказов, выручки и штук по товарам, скорее всего эти признаки не повышают качество;\n",
    "- \"Выбросы\" - это \"VIP\" клиенты для компании, а не ошибка в данных."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 5. Выбор метрики."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Для задач бинарной классификации обычно используются следующие метрики:\n",
    "\n",
    "- Доля правильных ответов(Accuracy)\n",
    "- Полнота(Recall)\n",
    "- Точность (Precision)\n",
    "- Среднее гармоническое Recall и Precision (F1-Score)\n",
    "- LogLoss\n",
    "- Площадь по ROC-кривой (ROC-AUC)\n",
    "\n",
    "В данной задаче целевой класс несбалансирован (85%/15%).Также для применения модели в жизни нужно оценивать вероятность того, что клиент не сделает покупку в следующем промежутке времени, чтобы выбрать оптимальный порог принятия решения на кого воздействовать различными способами. Таким образом, наиболее подходящей метрикой является **ROC-AUC**.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 6. Выбор модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве моделей будут использоваться:\n",
    "\n",
    "- Логистическую регрессию(LogisticRegression)\n",
    "- Случайный лес(RandomForestClassifier)\n",
    "- Градиентный бустинг над деревьями (xgboost, lightgbm)\n",
    "\n",
    "В принципе, это наиболее часто используемые модели для задач классификаций, где обучающая выборка не очень большая и не сильно разряженная. У каждой модели есть свои плюсы для решаемой задачи:\n",
    "\n",
    "- Логистическая регрессия хороша тем, что легко можно запустить в production, есть интепритируемость;\n",
    "- Случайный лес - даёт хорошее качество без настройки гиперпараметров, не склонен к переобучению;\n",
    "- Градиентный бустинг - возможность получить лучшее качество при настройки гиперпараметров. В данной задаче попробуем две реализации данного типа модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 7-9. Предобработка данных. Кросс-валидация и настройка гиперпараметров модели.Создание новых признаков и описание этого процесса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проведена предобработка данных для конкретной модели. При необходимости есть и описано масштабирование признаков, заполнение пропусков, замены строк на числа, OheHotEncoding, обработка выбросов, отбор признаков с описанием используемых для этого методов. Корректно сделано разбиение данных на обучающую и отложенную части;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Из категориальных кризнаков у нас только месяц, сделаем OHE. Удалим столбец \"Month\".**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df=pd.concat([df,pd.get_dummies(df['Month'], prefix='M', prefix_sep='_')],axis=1)\n",
    "df=df.drop(columns='Month')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "У нас данные представлены за период июль 2016 - декабрь 2017. Для корректной валидации нужно учитывать временную составляющую. На всякий случай отсортируем данные по столбцу \"Y_M\" и удалим этот признак."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df=df.sort_values(by='Y_M')\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.tail()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df=df.drop(columns='Y_M')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разобьём всю выборку на обучающую и проверочную в соотношении 9:1. В проверочную часть попадут данные за 2 последних месяца."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "idx = int(df.shape[0]*0.9)\n",
    "df_train = df.iloc[:idx,:]\n",
    "df_valid = df.iloc[idx:,:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.shape,df_train.shape,df_valid.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как уже говорилиось, у нас есть временная зависимость, поэтому для правильной валидации будем использовать sklearn.TimeSeriesSplit. Сделаем 10 фолдов, чтобы валидироваться на выборке соизмеримой с df_valid."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tscv=TimeSeriesSplit(n_splits=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на качество моделей без настроек параметров на исходных признаках, для LogisticRegression сделаем стандартизацию признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train = df_train.drop(columns='target')\n",
    "y_train = df_train['target']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lr = LogisticRegression()\n",
    "rf = RandomForestClassifier()\n",
    "lg = lgbmc()\n",
    "xg = xgbc()\n",
    "cb = catc()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "std = StandardScaler()\n",
    "lr_pipeline = make_pipeline(std,lr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "lrcv = cross_val_score(lr_pipeline,X_train,y_train,cv=tscv,scoring='roc_auc')\n",
    "rfcv = cross_val_score(rf,X_train,y_train,cv=tscv,scoring='roc_auc')\n",
    "lgcv = cross_val_score(lg,X_train,y_train,cv=tscv,scoring='roc_auc')\n",
    "xgcv = cross_val_score(xg,X_train,y_train,cv=tscv,scoring='roc_auc')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print ('lr_cv_score',np.mean(lrcv),\"+-\",np.std(lrcv))\n",
    "print ('rf_cv_score',np.mean(rfcv),\"+-\",np.std(rfcv))\n",
    "print ('lg_cv_score',np.mean(lgcv),\"+-\",np.std(lgcv))\n",
    "print ('xg_cv_score',np.mean(xgcv),\"+-\",np.std(xgcv))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На кроссвалидации лучший результат оказался у xgboost, худший - у случайного леса, логистическая регрессия показала результат немного ниже, чем у бустинга.\n",
    "\n",
    "Попробуем улучшить результат с помощью добавлением новых признаков и убиранием малоинформативных, а также настройкой гиперпараметров."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим признаки: Общее количество заказов за период, количество закупок (в месяцах), количество \"топовых\" товарных групп за 7 месяцев."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_train['Orders_total']=df_train.iloc[:,15:22].sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(np.log1p(df_train[df_train['target']==1]['Orders_total'].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='r');\n",
    "sns.distplot(np.log1p(df_train[df_train['target']==0]['Orders_total'].apply(lambda x: 0 if x<0 else x)),kde=False,norm_hist=True,color='g');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_train['B_M']=np.array([df_train.iloc[:,i].apply(lambda x: 1 if x >0 else 0) for i in range(0,6)]).sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.factorplot(y='target',x='B_M',data=df_train,kind='bar');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_train['TG_total']=np.array([df_train.iloc[:,i].apply(lambda x: 1 if x >0 else 0) for i in range(200,270)]).sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.factorplot(y='target',x='TG_total',data=df_train,kind='bar',aspect=5);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По всем трём признакам видна закономерность - чем меньше значение, тем выше доля оттока.\n",
    "Проверим качество с добавлением этих признаков для xgboost."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train = df_train.drop(columns='target')\n",
    "y_train = df_train['target']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xgcv = cross_val_score(xg,X_train,y_train,cv=tscv,scoring='roc_auc')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print ('xg_cv_score',np.mean(xgcv),\"+-\",np.std(xgcv))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество не улучшилось. Настроим параметры на кросс-валидации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "XG_params = {'n_estimators': [100,200,300,400,500],\n",
    "            'seed':[17],\n",
    "            'max_depth': [3,4,5,6,7,8],\n",
    "            'learning_rate': [0.01,0.05,0.1]}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xggs = GridSearchCV(xg,param_grid=XG_params,scoring='roc_auc',cv=tscv)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Часть 10. Прогноз для тестовой или отложенной выборки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xg.fit(X_train,y_train)\n",
    "df_valid['Orders_total']=df_valid.iloc[:,15:22].sum(axis=1)\n",
    "df_valid['B_M']=np.array([df_valid.iloc[:,i].apply(lambda x: 1 if x >0 else 0) for i in range(0,6)]).sum(axis=0)\n",
    "df_valid['TG_total']=np.array([df_valid.iloc[:,i].apply(lambda x: 1 if x >0 else 0) for i in range(200,270)]).sum(axis=0)\n",
    "\n",
    "X_valid = df_valid.drop(columns='target')\n",
    "y_valid = df_valid['target']\n",
    "\n",
    "y_pred_proba=xg.predict_proba(X_valid)[:,1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "roc_auc_score(y_valid,y_pred_proba)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат получился выше, чем на валидации. Это связано с тем, что у нас была TimeSeriesValidation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Часть 11. Выводы "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "После исследования и построения модели можно сделать следюущие выводы:\n",
    "    \n",
    "1)В принципе, получена модель с неплохим качеством, которая может помогать определять клиентов, которые не закупятся в ближайшие 3 месяца.\n",
    "\n",
    "2)Возможные пути улучшения модели - добавить признаки другого типа (взаимодействия с клиентами), покрутить признаки.\n",
    "\n",
    "3)Логистическая регрессия показала неплохие результаты, можно попробовать немного поднастроить её вместо построения многих деревьев.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
